﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using Creative85.Framework.Data;
using Project32CHE.Biz.Goods;
using Project32CHE.Biz.Goods.Extend.Exceptions;
using Project32CHE.Biz.Goods.Extend.Model;
using Project32CHE.Biz.Goods.Extend.Providers;
using Project32CHE.Biz.Unionshop;

namespace Project32CHE.Biz.Goods.Extend
{
    /// <summary>
    /// 库存服务
    /// </summary>
    public sealed partial class InventoryService
    {
    }

    public sealed class InventoryItem
    {
        public Guid StorageId { get; set; }
        public Guid GoodsId { get; set; }
        public decimal OriginalQuantity { get; set; }
        public decimal ChangeQuantity { get; set; }
        public decimal CurrentQuantity { get; set; }
        public InventoryOperate Operate { get; set; }
        public DateTime CreateTime { get; set; }

        internal Guid InventoryItemId { get; set; }
    }
    class InventoryService_new : IDisposable
    {
        public InventoryService_new()
        {
            this.DbContext = Db.Instance;
        }
        public InventoryService_new(Db db)
        {
            this.DbContext = db;
        }

        protected Db DbContext { get; private set; }
        public Guid StorageId { get; private set; }

        protected InventoryItem GetItem(Guid goodsId)
        {
            var storageId = this.StorageId;
            if (storageId.Equals(Guid.Empty)) { throw new NotSupportedException("库存服务：错误的仓库信息！"); }
            var storageItem = this.DbContext.Query<StorageItem>("FROM StorageItem WITH(UPDLOCK) WHERE StorageID=@0 AND GoodsID=@1", storageId, goodsId).SingleOrDefault();
            if (storageItem == null)
            {
                //到这里时，上面的锁由于未查到记录，导致无法上锁。
                //这里需要将锁范围放大至商品上，然后插入，最后再锁一次库存锁
                this.BeginTransaction();
                try
                {
                    var goods = this.DbContext.Query<Goods.Model.Goods>("FROM GoodsInfo WITH(UPDLOCK) WHERE ID=@0", goodsId).Single();
                    /*
                     * 思路：
                     * 如果A、B两人同时在当前仓库创建当前商品库存，A、B在执行到上面查询商品信息的时候只有一个人（假设是A）通过，B等着A结束。
                     * 如果不加下面这句，会导致B在A结束后又一次创建了库存，导致库存信息重复。
                     * 如果加了下面这句，B会再一次确认是否存在库存，如果存在，则使用存在的库存信息继续处理。
                     */
                    storageItem = this.DbContext.Query<StorageItem>("FROM StorageItem WITH(UPDLOCK) WHERE StorageID=@0 AND GoodsID=@1", storageId, goodsId).SingleOrDefault();
                    if (storageItem == null)
                    {
                        var time = DateTime.Now;
                        storageItem = new StorageItem()
                        {
                            ID = Guid.NewGuid(),
                            UnionID = goods.UnionID,
                            ShopID = goods.ShopID,
                            StorageID = storageId,
                            GoodsID = goods.ID,
                            GoodsCode = goods.GoodsCode,
                            GoodsName = goods.GoodsName,
                            Qty = 0,
                            CreateTime = time,
                            LastTime = time
                        };
                        this.DbContext.Insert(storageItem);
                    }
                    ////由于上面的插入操作自带锁，并且与更新锁互斥，这里可以不用再一次锁定。
                    //storageItem = this.DbContext.Query<StorageItem>("FROM StorageItem WITH(UPDLOCK) WHERE StorageID=@0 AND GoodsID=@1", storageId, goodsId).Single();

                    this.CompleteTransaction();
                }
                catch
                {
                    this.AbortTransaction();
                    throw;
                }
            }
            var item = new InventoryItem()
            {
                CreateTime = DateTime.Now,
                Operate = InventoryOperate.Default,

                InventoryItemId = storageItem.ID,

                StorageId = storageItem.StorageID,
                GoodsId = storageItem.GoodsID,
                OriginalQuantity = storageItem.Qty,
                ChangeQuantity = 0,
                CurrentQuantity = storageItem.Qty
            };
            return item;
        }
        protected void UpdateItem(InventoryItem item)
        {
            this.DbContext.Update(
                new StorageItem()
                {
                    ID = item.InventoryItemId,
                    Qty = item.CurrentQuantity,
                    LastTime = DateTime.Now
                },
                x => new
                {
                    x.Qty,
                    x.LastTime
                });
        }

        public void ChangeStorage(Guid storageId)
        {
            if (storageId.Equals(this.StorageId)) { return; }
            this.StorageId = storageId;
        }

        public InventoryItem Force(Guid goodsId, decimal quantity, Guid? storageId = null)
        {
            if (storageId.HasValue) { this.ChangeStorage(storageId.Value); }

            this.BeginTransaction();
            try
            {
                var item = this.GetItem(goodsId);

                item.Operate = InventoryOperate.Force;
                item.ChangeQuantity = quantity;
                item.CurrentQuantity = item.ChangeQuantity;

                this.UpdateItem(item);

                this.CompleteTransaction();

                return item;
            }
            catch
            {
                this.AbortTransaction();
                throw;
            }
        }
        public InventoryItem Add(Guid goodsId, decimal quantity, Guid? storageId = null)
        {
            if (storageId.HasValue) { this.ChangeStorage(storageId.Value); }

            this.BeginTransaction();
            try
            {
                var item = this.GetItem(goodsId);

                item.Operate = InventoryOperate.StoreIn;
                item.ChangeQuantity = quantity;
                item.CurrentQuantity = item.OriginalQuantity + item.ChangeQuantity;

                this.UpdateItem(item);

                this.CompleteTransaction();

                return item;
            }
            catch
            {
                this.AbortTransaction();
                throw;
            }
        }
        public InventoryItem Cut(Guid goodsId, decimal quantity, Guid? storageId = null)
        {
            if (storageId.HasValue) { this.ChangeStorage(storageId.Value); }

            this.BeginTransaction();
            try
            {
                var item = this.GetItem(goodsId);

                item.Operate = InventoryOperate.DeliveryOut;
                item.ChangeQuantity = quantity;
                item.CurrentQuantity = item.OriginalQuantity - item.ChangeQuantity;

                this.UpdateItem(item);

                this.CompleteTransaction();

                return item;
            }
            catch
            {
                this.AbortTransaction();
                throw;
            }
        }

        #region 事务处理
        private bool _transactionBegined = false;
        private bool _transactionIsAborted = false;
        private int _transactionCount = 0;

        public void BeginTransaction()
        {
            if (!this._transactionBegined)
            {
                this.DbContext.BeginTransaction();
                this._transactionBegined = true;
                this._transactionIsAborted = false;
                this._transactionCount = 0;
            }
            if (this._transactionBegined)
            {
                this._transactionCount++;
            }
        }
        public void AbortTransaction(bool fromComplete = false)
        {
            if (this._transactionBegined)
            {
                if (!fromComplete)
                {
                    this._transactionCount--;
                    if (this._transactionCount >= 1)
                    {
                        this._transactionIsAborted = true;
                        return;
                    }
                }
                this.DbContext.AbortTransaction();
                this.privateComplateTransaction();
            }
        }
        public void CompleteTransaction()
        {
            if (this._transactionBegined)
            {
                this._transactionCount--;
                if (this._transactionCount < 1)
                {
                    if (this._transactionIsAborted)
                    {
                        this.AbortTransaction(true);
                    }
                    else
                    {
                        this.DbContext.CompleteTransaction();
                        this.privateComplateTransaction();
                    }
                }
            }
        }

        private void privateComplateTransaction()
        {
            this._transactionBegined = false;
            this._transactionIsAborted = false;
            this._transactionCount = 0;
        }
        #endregion

        #region IDisposable 成员

        protected bool disposed = false;
        /// <summary>
        /// 非密封类修饰用protected virtual
        /// 密封类修饰用private
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (this.disposed)
            {
                return;
            }
            if (disposing)
            {
                // 清理托管资源
                if (this._transactionBegined)
                {
                    this.AbortTransaction(true);
                    throw new NotSupportedException("库存服务结束时存在未结束的事务，该事务已被强制回滚！");
                }
            }
            // 清理非托管资源
            {
                this.DbContext = null;
            }
            //让类型知道自己已经被释放
            this.disposed = true;
        }

        /// <summary>
        /// 必须，以备忘记了显式调用Dispose方法
        /// </summary>
        ~InventoryService_new()
        {
            //必须为false
            this.Dispose(false);
        }

        public void Dispose()
        {
            //必须为true
            this.Dispose(true);
            //通知垃圾回收机制不再调用终结器（析构器）
            GC.SuppressFinalize(this);
        }

        #endregion
    }

    public class InventoryManager
    {
        private AbstractProvider provider = null;
        private InventorySheet sheet = null;

        public InventoryManager(string type, Guid id)
        {
            this.provider = ProviderFactory.Get(type, id);
        }

        private void process()
        {
            var provider = this.provider;
            var sheet = this.sheet;
            var parts = sheet.Parts;
            var db = Db.Instance;
            var service = new InventoryService_new(db);
            InventoryItem item = null;
            service.BeginTransaction();
            try
            {
                if (sheet.StorageId.HasValue)
                {
                    service.ChangeStorage(sheet.StorageId.Value);
                }
                var details = new List<InventoryDetail>();
                var lowInventorys = new List<LowInventoryItem>();
                foreach (var part in parts)
                {
                    var inventoryQuantity = 0M;
                    if (sheet.IsChangeCostPrice)
                    {
                        inventoryQuantity = db.Query<StorageItem>("FROM StorageItem WITH(UPDLOCK) WHERE GoodsID=@0", part.GoodsId).Sum(x => x.Qty);
                    }
                    //DONE:这里不能限制数量为0。特例：盘点单会出现这种情况
                    if (part.Quantity <= 0) { throw new ArgumentOutOfRangeException("part.Quantity", string.Format("不支持的库存数量【{0}】", part.Quantity)); }

                    //item = null;
                    switch (part.Operator)
                    {
                        case InventoryOperate.StoreIn:
                            item = service.Add(part.GoodsId, part.Quantity, part.StorageId);
                            break;
                        case InventoryOperate.DeliveryOut:
                            item = service.Cut(part.GoodsId, part.Quantity, part.StorageId);
                            break;
                        default:
                            throw new NotSupportedException("不支持的库存操作");
                    }

                    if (item.CurrentQuantity < 0)
                    {
                        lowInventorys.Add(new LowInventoryItem() { GoodsId = item.GoodsId, StorageId = service.StorageId, CurrentQuantity = item.CurrentQuantity });
                    }
                    //if (item == null) { continue; }

                    var goods = db.Query<Goods.Model.Goods>("FROM GoodsInfo WITH(UPDLOCK) WHERE ID=@0", item.GoodsId).Single();
                    var detail = new InventoryDetail()
                    {
                        ID = Guid.NewGuid(),

                        UnionID = sheet.UnionId,
                        ShopID = sheet.ShopId,

                        BizType = (string.IsNullOrWhiteSpace(sheet.Type) ? provider.Type : sheet.Type),
                        BizID = sheet.Id,
                        BizCode = sheet.Code,
                        BizDate = sheet.DateTime,
                        BizOperatorID = sheet.OperatorId,
                        BizOperatorName = sheet.OperatorName,

                        StorageID = service.StorageId,
                        GoodsID = goods.ID,
                        CreateTime = item.CreateTime,

                        InventoryType = null,
                        InPrice = null,
                        InQty = null,
                        OutPrice = null,
                        OutQty = null,

                        RemainCostPrice = 0,
                        RemainQty = 0,

                        SortID = part.SortId
                    };
                    switch (part.Operator)
                    {
                        case InventoryOperate.StoreIn:
                            detail.InQty = part.Quantity;
                            detail.InPrice = part.SalePrice;

                            detail.InventoryType = "入库";
                            detail.RemainQty = item.CurrentQuantity;
                            break;
                        case InventoryOperate.DeliveryOut:
                            detail.OutQty = part.Quantity;
                            detail.OutPrice = part.SalePrice;

                            detail.InventoryType = "出库";
                            detail.RemainQty = item.CurrentQuantity;
                            break;
                    }
                    detail.RemainCostPrice = goods.CostPrice;
                    if (sheet.IsChangeCostPrice && part.Quantity != 0)
                    {
                        var newCostPrice = PriceService.CalculateCostPrice(part.Operator, inventoryQuantity, goods.CostPrice, part.Quantity, (part.CostPrice.HasValue ? part.CostPrice.Value : goods.CostPrice));
                        if (goods.CostPrice != newCostPrice)
                        {
                            detail.RemainCostPrice = newCostPrice;

                            goods.CostPrice = newCostPrice;
                            db.Update(goods, x => new { x.CostPrice });
                        }
                    }
                    details.Add(detail);
                }
                if (lowInventorys.Count > 0)
                {
                    var list = lowInventorys
                       .GroupBy(x => x.GoodsId.ToString() + x.StorageId.ToString())
                       .Select(x => x.OrderBy(y => y.CurrentQuantity).First())
                       .ToList();
                    throw new LowInventoryException(list);
                }

                foreach (var detail in details)
                {
                    db.Insert(detail);
                }

                service.CompleteTransaction();
            }
            catch
            {
                service.AbortTransaction();
                throw;
            }
        }

        public void Process(params object[] parameters)
        {
            this.sheet = this.provider.Process(parameters);
            this.process();
        }
        public void UnProcess(params object[] parameters)
        {
            this.sheet = this.provider.UnProcess(parameters);
            this.process();
        }
    }
}
